import logging
import os
from datetime import datetime
from functools import partial
from unittest.mock import MagicMock

import pytest
from click.testing import CliRunner

from notifiers.core import get_notifier
from notifiers.core import Provider
from notifiers.core import ProviderResource
from notifiers.core import Response
from notifiers.core import SUCCESS_STATUS
from notifiers.logging import NotificationHandler
from notifiers.providers import _all_providers
from notifiers.utils.helpers import text_to_bool
from notifiers.utils.schema.helpers import list_to_commas
from notifiers.utils.schema.helpers import one_or_more

log = logging.getLogger(__name__)


class MockProxy:
    name = "mock_provider"


class MockResource(MockProxy, ProviderResource):
    resource_name = "mock_resource"

    _required = {"required": ["key"]}
    _schema = {
        "type": "object",
        "properties": {
            "key": {"type": "string", "title": "required key"},
            "another_key": {"type": "integer", "title": "non-required key"},
        },
        "additionalProperties": False,
    }

    def _get_resource(self, data: dict):
        return {"status": SUCCESS_STATUS}


class MockProvider(MockProxy, Provider):
    """Mock Provider"""

    base_url = "https://api.mock.com"
    _required = {"required": ["required"]}
    _schema = {
        "type": "object",
        "properties": {
            "not_required": one_or_more(
                {"type": "string", "title": "example for not required arg"}
            ),
            "required": {"type": "string"},
            "option_with_default": {"type": "string"},
            "message": {"type": "string"},
        },
        "additionalProperties": False,
    }
    site_url = "https://www.mock.com"

    @property
    def defaults(self):
        return {"option_with_default": "foo"}

    def _send_notification(self, data: dict):
        return Response(status=SUCCESS_STATUS, provider=self.name, data=data)

    def _prepare_data(self, data: dict):
        if data.get("not_required"):
            data["not_required"] = list_to_commas(data["not_required"])
        data["required"] = list_to_commas(data["required"])
        return data

    @property
    def resources(self):
        return ["mock_rsrc"]

    @property
    def mock_rsrc(self):
        return MockResource()


@pytest.fixture(scope="session")
def mock_provider():
    """Return a generic :class:`notifiers.core.Provider` class"""
    _all_providers.update({MockProvider.name: MockProvider})
    return MockProvider()


@pytest.fixture
def bad_provider():
    """Returns an unimplemented :class:`notifiers.core.Provider` class for testing"""

    class BadProvider(Provider):
        pass

    return BadProvider


@pytest.fixture
def bad_schema():
    """Return a provider with an invalid JSON schema"""

    class BadSchema(Provider):
        _required = {"required": ["fpp"]}
        _schema = {"type": "banana"}

        name = "bad_schmea"
        base_url = ""
        site_url = ""

        def _send_notification(self, data: dict):
            pass

    return BadSchema


@pytest.fixture(scope="class")
def provider(request):
    name = getattr(request.module, "provider", None)
    if not name:
        pytest.fail(f"Test class '{request.module}' has not 'provider' attribute set")
    p = get_notifier(name)
    if not p:
        pytest.fail(f"No notifier with name '{name}'")
    return p


@pytest.fixture(scope="class")
def resource(request, provider):
    name = getattr(request.cls, "resource", None)
    if not name:
        pytest.fail(f"Test class '{request.cls}' has not 'resource' attribute set")
    resource = getattr(provider, name, None)
    if not resource:
        pytest.fail(f"Provider {provider.name} does not have a resource named {name}")
    return resource


@pytest.fixture
def cli_runner(monkeypatch):
    from notifiers_cli.core import notifiers_cli, provider_group_factory

    monkeypatch.setenv("LC_ALL", "en_US.utf-8")
    monkeypatch.setenv("LANG", "en_US.utf-8")
    provider_group_factory()
    runner = CliRunner()
    return partial(runner.invoke, notifiers_cli, obj={})


@pytest.fixture
def magic_mock_provider(monkeypatch):
    MockProvider.notify = MagicMock()
    MockProxy.name = "magic_mock"
    monkeypatch.setitem(_all_providers, MockProvider.name, MockProvider)
    return MockProvider()


@pytest.fixture
def handler(caplog):
    def return_handler(provider_name, logging_level, data=None, **kwargs):
        caplog.set_level(logging.INFO)
        hdlr = NotificationHandler(provider_name, data, **kwargs)
        hdlr.setLevel(logging_level)
        return hdlr

    return return_handler


def pytest_runtest_setup(item):
    """Skips PRs if secure env vars are set and test is marked as online"""
    pull_request = text_to_bool(os.environ.get("TRAVIS_PULL_REQUEST"))
    secure_env_vars = text_to_bool(os.environ.get("TRAVIS_SECURE_ENV_VARS"))
    online = item.get_closest_marker("online")
    if online and pull_request and not secure_env_vars:
        pytest.skip("skipping online tests via PRs")


@pytest.fixture
def test_message(request):
    message = os.environ.get("TRAVIS_BUILD_WEB_URL") or "Local test"
    return f"{message}-{request.node.name}-{datetime.now().isoformat()}"
